/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "commonheaders.h"
#include "classTzManager.h"
#include "ctrl.h"

/**************************************************************************************************
 * struct CTimeZone
 **************************************************************************************************/

/**
 * This is the default constructure, which resets
 * all attributes to NULL.
 **/
CTimeZone::CTimeZone()
{
	memset( this, 0, sizeof( *this ) );
}

/**
 * The default construcor's task ist to clear out
 * all pieces of the used memory.
 **/
CTimeZone::~CTimeZone()
{
	if( pszDisplay )
	{
		mir_free( pszDisplay );
		pszDisplay = NULL;
	}
}

/**
 * This method can be used to basically convert a Windows
 * timezone to the format, known by miranda.
 *
 * @warning	This operation does not work vice versa in
 *					all cases, as there are sometimes more then
 *					one Windows timezones with the same Bias.
 **/
BYTE CTimeZone::ToMirandaTimezone() const
{
	return ( BYTE ) ( Bias / 30 );
}

/**
 * This operator translates the content of this object to
 * a TIME_ZONE_INFORMATION structure as it is required by 
 * several windows functions.
 **/
CTimeZone::operator TIME_ZONE_INFORMATION() const
{
	TIME_ZONE_INFORMATION tzi;

	tzi.Bias = Bias;
	tzi.DaylightBias = DaylightBias;
	tzi.StandardBias = StandardBias;

	memcpy( &tzi.DaylightDate, &DaylightDate, sizeof( DaylightDate ) );
	memcpy( &tzi.StandardDate, &StandardDate, sizeof( DaylightDate ) );
	//memset( tzi.DaylightName, 0, sizeof( tzi.DaylightName ) );
	//memset( tzi.StandardName, 0, sizeof( tzi.StandardName ) );

	return tzi;
}		

/***********************************************************************************************************
 * class CTzBiasIdx
 ***********************************************************************************************************/

INT	CTzByBias::sortFunc( const CTimeZone *tz1, const CTimeZone *tz2 )
{
	return ( tz2->Bias < tz1->Bias )
		? -1 : ( tz2->Bias > tz1->Bias ? 1 : mir_tcscmp( tz1->pszDisplay, tz2->pszDisplay ) ) ;	
}

CTzByBias::CTzByBias() : LIST<CTimeZone>( 50, ( FTSortFunc ) CTzByBias::sortFunc )
{
}

CTzByBias::~CTzByBias()
{
	destroy();
}

CTimeZone* CTzByBias::find( LONG Bias, LPTSTR pszDisplay ) const
{
	CTimeZone key;
	key.Bias = Bias;
	key.pszDisplay = pszDisplay;
	return find( &key );
}

/***********************************************************************************************************
 * class CTzByIndex
 ***********************************************************************************************************/

INT	CTzByIndex::sortFunc( const CTimeZone *tz1, const CTimeZone *tz2 )
{
	return tz1->dwIndex < tz2->dwIndex ? -1 : tz1->dwIndex > tz2->dwIndex;	
}

CTzByIndex::CTzByIndex() : LIST<CTimeZone>( 50, ( FTSortFunc ) CTzByIndex::sortFunc )
{
}

CTzByIndex::~CTzByIndex()
{
	destroy();
}

/**
 * This method clears the TzManager's data.
 **/		
VOID CTzByIndex::destroy( VOID )
{
	INT i;

	// delete data
	for( i = 0; i < getCount(); i++ )
	{
		delete (*this)[i];
	}
	// delete the list
	LIST<CTimeZone>::destroy();
}

CTimeZone* CTzByIndex::find( DWORD idx ) const
{
	if( idx != ( DWORD ) -1 )
	{	
		CTimeZone key;
		key.dwIndex = idx;
		return find( &key );
	}
	return NULL;
}

/***********************************************************************************************************
 * class CTzByName
 ***********************************************************************************************************/

INT	CTzByName::sortFunc( const CTimeZone *tz1, const CTimeZone *tz2 )
{
	return ( tz2->Bias < tz1->Bias )
		? -1 : ( tz2->Bias > tz1->Bias ? 1 : mir_tcscmp( tz1->pszName, tz2->pszName ) ) ;	
}

CTzByName::CTzByName() : LIST<CTimeZone>( 50, ( FTSortFunc ) CTzByName::sortFunc )
{
}

CTzByName::~CTzByName()
{
	destroy();
}

/**
 * This method clears the TzManager's data.
 **/		
VOID CTzByName::destroy( VOID )
{
	INT i;

	// delete data
	for( i = 0; i < getCount(); i++ )
	{
		delete (*this)[i];
	}
	// delete the list
	LIST<CTimeZone>::destroy();
}

CTimeZone* CTzByName::find( LPTSTR pszName ) const
{
	CTimeZone key;
	key.pszName = pszName;
	return find( &key );
}

/***********************************************************************************************************
 * class CTzManager
 ***********************************************************************************************************/

/**
 *
 *
 **/
CTzManager::CTzManager()
	: _bias(), Bias( _bias ),
	_index(), Index( _index ),
	_name(), Name( _name )
{
}

/**
 * This method loads all information about timezones from windows' registry.
 **/
INT CTzManager::GetTimeZonesFromRegistry()
{
	INT		result;
	HKEY	hKeyRoot, hKeyTz;
	DWORD	i, cbData;

	bool win9x = false;
	TCHAR	szName[MAX_PATH],
			szDisplay[MAX_PATH],
			szDLT[MAX_PATH],
			szSTD[MAX_PATH];

	result = RegOpenKey(HKEY_LOCAL_MACHINE, _T( TZREG ), &hKeyRoot);
	if( result != ERROR_SUCCESS ) {
		win9x = true;
		result = RegOpenKey(HKEY_LOCAL_MACHINE, _T( TZREG_9X ), &hKeyRoot);
	}
	if( result == ERROR_SUCCESS )
	{
		// clear out old list
		_index.destroy();
		_bias.destroy();

		myGlobals.TzIndexExist = true;	// true on 1 start
		for( i = 0; ERROR_SUCCESS == RegEnumKey( hKeyRoot, i, szName, SIZEOF( szName ) ); i++ )
		{
			result = RegOpenKey( hKeyRoot, szName, &hKeyTz );
			if( result == ERROR_SUCCESS )
			{
				CTimeZone	*tz = new CTimeZone();

				if( tz )
				{
					cbData = sizeof( szDisplay );
					result |= RegQueryValueEx( hKeyTz, _T("Display"), 0, 0, ( LPBYTE ) szDisplay, &cbData );

					cbData = sizeof( szDLT );
					result |= RegQueryValueEx( hKeyTz, _T("Dlt"), 0, 0, ( LPBYTE ) szDLT, &cbData );

					cbData = sizeof( szSTD );
					result |= RegQueryValueEx( hKeyTz, _T("Std"), 0, 0, ( LPBYTE ) szSTD, &cbData );

					cbData = sizeof( DWORD );
					// not all OS has "Index" info in reg
					if(RegQueryValueEx( hKeyTz, _T("Index"), 0, 0, ( LPBYTE ) &tz->dwIndex, &cbData )== ERROR_FILE_NOT_FOUND) {
						if (myGlobals.TzIndexExist) 
							myGlobals.TzIndexExist = false;
						tz->dwIndex = (DWORD) -100;
					}

					cbData = sizeof( REG_TIME_ZONE_INFORMATION );
					result |= RegQueryValueEx( hKeyTz, _T("TZI"), 0, 0, ( LPBYTE ) tz, &cbData );

					if( !result && _index.insert( tz ) )
					{
						tz->pszName		= mir_tcsdup( szName );
						tz->pszDisplay	= mir_tcsdup( szDisplay );
						tz->pszDLT		= mir_tcsdup( szDLT );
						tz->pszSTD		= mir_tcsdup( szSTD );
						_bias.insert( tz );
					}
					else
					{
						delete tz;
						tz = NULL;
					}
				}
				RegCloseKey( hKeyTz );
			}
		}
		RegCloseKey( hKeyRoot );
	}
	return result;
}

/**
 * This method trys to find some default windows timezone idices for a given
 * miranda timezone.
 *
 * @param		MirTz		- this is a miranda timezone with values between -24 and 24.
 *
 * @return		This method returns the index of a windows timezone, which is maps
 *				the @MirTz value or -1 if no windows timezone index exists.
 **/
TZ_MAP CTzManager::IndexFromMirandaTimeZone( const CHAR MirTz ) const
{
	/**
	 * This is an item of an array of timezones, which are known by both Miranda-IM
	 * and Windows. It is used to map an ICQ timezone against a Windows timezone
	 * for retrieving information about daylight saving time and more.
	 **/
	//static const DWORD TzMap[] = {
	static const TZ_MAP TzMap[] = {
		{ 0,	_T("Dateline Standard Time")},			// GMT-12:00 Eniwetok; Kwajalein
		{-1,	_T("")},								// GMT-11:30
		{ 1,	_T("Samoa Standard Time")},				// GMT-11:00 Midway Island; Samoa
		{-1,	_T("")},								// GMT-10:30
		{ 2,	_T("Hawaiian Standard Time")},			// GMT-10:00 Hawaii
		{-1,	_T("")},								// GMT-9:30
		{ 3,	_T("Alaskan Standard Time")},			// GMT-9:00 Alaska
		{-1,	_T("")},								// GMT-8:30
		{ 4,	_T("Pacific Standard Time")},			// GMT-8:00 Pacific Time; Tijuana
		{-1,	_T("")},								// GMT-7:30
		{15,	_T("US Mountain Standard Time")},		// GMT-7:00 Arizona; Mountain Time
		{-1,	_T("")},								// GMT-6:30
		{33,	_T("Central America Standard Time")},	// GMT-6:00 Central Time; Central America; Saskatchewan
		{-1,	_T("")},								// GMT-5:30
		{45,	_T("SA Pacific Standard Time")},		// GMT-5:00 Eastern Time; Bogota; Lima; Quito
		{-1,	_T("")},								// GMT-4:30
		{56,	_T("Pacific SA Standard Time")},		// GMT-4:00 Atlantic Time; Santiago; Caracas; La Paz
		{60,	_T("Newfoundland Standard Time")},		// GMT-3:30 Newfoundland
		{70,	_T("SA Eastern Standard Time")},		// GMT-3:00 Greenland; Buenos Aires; Georgetown
		{-1,	_T("")},								// GMT-2:30
		{75,	_T("Mid-Atlantic Standard Time")},		// GMT-2:00 Mid-Atlantic
		{-1,	_T("")},								// GMT-1:30
		{80,	_T("Azores Standard Time")},			// GMT-1:00 Cape Verde Islands; Azores
		{-1,	_T("")},								// GMT-0:30
		{85,	_T("GMT Standard Time")},				// GMT+0:00 London; Dublin; Edinburgh; Lisbon; Casablanca
		{-1,	_T("")},								// GMT+0:30
		{105,	_T("Romance Standard Time")},			// GMT+1:00 Central European Time; West Central Africa; Warsaw
		{-1,	_T("")},								// GMT+1:30
		{140,	_T("South Africa Standard Time")},		// GMT+2:00 Jerusalem; Helsinki; Harare; Cairo; Bucharest; Athens
		{-1,	_T("")},								// GMT+2:30
		{145,	_T("Russian Standard Time")},			// GMT+3:00 Moscow; St. Petersburg; Nairobi; Kuwait; Baghdad
		{160,	_T("Iran Standard Time")},				// GMT+3:30 Tehran
		{165,	_T("Arabian Standard Time")},			// GMT+4:00 Baku; Tbilisi; Yerevan; Abu Dhabi; Muscat
		{175,	_T("Afghanistan Standard Time")},		// GMT+4:30 Kabul
		{185,	_T("West Asia Standard Time")},			// GMT+5:00 Calcutta; Chennai; Mumbai; New Delhi; Ekaterinburg
		{200,	_T("Sri Lanka Standard Time")},			// GMT+5:30 Sri Jayawardenepura
		{201,	_T("N. Central Asia Standard Time")},	// GMT+6:00 Astana; Dhaka; Almaty; Novosibirsk
		{203,	_T("Myanmar Standard Time")},			// GMT+6:30 Rangoon
		{207,	_T("North Asia Standard Time")},		// GMT+7:00 Bankok; Hanoi; Jakarta; Krasnoyarsk
		{-1,	_T("")},								// GMT+7:30
		{210,	_T("China Standard Time")},				// GMT+8:00 Perth; Taipei; Singapore; Hong Kong; Beijing
		{-1,	_T("")},								// GMT+8:30
		{235,	_T("Tokyo Standard Time")},				// GMT+9:00 Tokyo; Osaka; Seoul; Sapporo; Yakutsk
		{245,	_T("AUS Central Standard Time")},		// GMT+9:30 Darwin; Adelaide
		{270,	_T("Vladivostok Standard Time")},		// GMT+10:00 East Australia; Guam; Vladivostok
		{-1,	_T("")},								// GMT+10:30
		{280,	_T("Central Pacific Standard Time")},	// GMT+11:00 Magadan; Solomon Is.; New Caledonia
		{-1,	_T("")},								// GMT+11:30
		{290,	_T("New Zealand Standard Time")},		// GMT+12:00 Auckland; Wellington; Fiji; Kamchatka; Marshall Is.
		{-1,	_T("")}
	};

	return ( MirTz >= -24 && MirTz <= 24 ) ? TzMap[ 24 - MirTz ] : TzMap[ 49 ] ;
}

/**
 * This method trys to find the index for the contact's windows timezone. If no index is 
 * available for the contact, the @e dwIndex value retrieves the bias of miranda's timezone value.
 *
 * @warning	Make sure you convert @e dwIndex to CHAR if the function returns 1 in order to get
 * the correct miranda timezone!
 *
 * @param		hContact	- the HANDLE of the contact to read timezone information for
 * @param		szProto		- contact's protocol
 * @param		dwIndex		- This reference to an DWORD retrieves the index of the windows timezone,
 *							  set for a contact, if the function returns 0. If the index can't be 
 *							  determined, the variable retrieves the value for a miranda's 'timezone' setting
 *							  or -100 (unspecified) and the function returns 1.
 * @param		pszName		- This reference to an LPTSTR retrieves the name of the windows timezone,
 *
 * @retval	tz				- The index for a windows timezone was found for the contact.
 * @retval	NULL			- There is no index, but if the contact's 'timezone' setting is valid, @e dwIndex
 *							  retrieved its value. If not, dwIndex is -100 (unspecified).
 **/
CTimeZone* CTzManager::GetContactTimeZone( HANDLE hContact, LPCSTR szProto, DWORD &dwIndex, LPTSTR &pszName ) const
{
	CTimeZone *tz = NULL;

	// try to read windows' timezone Name from database
	pszName = DB::Setting::GetTString( hContact, USERINFO, "TzName" ) ;
	dwIndex = DB::Setting::GetDWord( hContact, USERINFO, "TzIndex", -100 );
	if( pszName ){
		// contact has new TzName
		tz = Name.find( pszName );
	}
	else if( dwIndex != ( DWORD )-100 && myGlobals.TzIndexExist == 1 ){
		// contact has old TzIndex and Win TzIndex info exist
		tz = Index.find( dwIndex );
	}
	if( !tz ){
		DBVARIANT dbv;
		TZ_MAP MirTZ;
		dbv.type = DBVT_DELETED;

		// try to get miranda's timezone value
		if( DB::Setting::GetAsIsEx( hContact, USERINFO, szProto, SET_CONTACT_TIMEZONE, &dbv ) || dbv.type != DBVT_BYTE )
		{
			// maybe a failure lets us read a string, so clear it out
			DB::Variant::Free( &dbv );
			dwIndex = ( DWORD ) -100;
		}
		else 
		{
			MirTZ = IndexFromMirandaTimeZone( dbv.cVal );
			dwIndex = MirTZ.Index;
			pszName = MirTZ.Name;
			tz = Index.find( dwIndex );
			if( !tz )
			{
				dwIndex = ( DWORD ) dbv.bVal;
			}
		}
	}
	return tz;
}

/**
 * This method trys to find the index for the contact's windows timezone. If no index is 
 * available for the contact, the @e dwIndex value retrieves the bias of miranda's timezone value.
 *
 * @warning	Make sure you convert @e dwIndex to CHAR if the function returns 1 in order to get
 * the correct miranda timezone!
 *
 * @param		hContact	- the HANDLE of the contact to read timezone information for
 * @param		szProto		- contact's protocol
 * @param		dwIndex		- This reference to an DWORD retrieves the index of the windows timezone,
 *							  set for a contact, if the function returns 0. If the index can't be 
 *							  determined, the variable retrieves the value for a miranda's 'timezone' setting
 *							  or -100 (unspecified) and the function returns 1.
 * @param		pszName		- This reference to an LPTSTR retrieves the name of the windows timezone,
 *
 * @retval	CTRLF_... flag	- The index for a windows timezone was found for the contact.
 * @retval	0				- There is no index, but if the contact's 'timezone' setting is valid,
 *							  @e dwIndex retrieved its value.	If not, dwIndex is -100 (unspecified).
 **/
WORD CTzManager::GetContactTimeZoneIndexCtrl( HANDLE hContact, LPCSTR szProto, DWORD& dwIndex, LPTSTR& pszName ) const
{
	WORD wFlags = 0;

	// try to read windows' timezone Name / Index from database
	pszName = DB::Setting::GetTString( hContact, USERINFO, "TzName" ) ;
	dwIndex = DB::Setting::GetDWord( hContact, USERINFO, "TzIndex", -1 );
	if( pszName){
		// contact has new TzName
		if( dwIndex != ( DWORD )-1)
			// delete old TzIndex if exist
			DB::Setting::Delete( hContact, USERINFO, "TzIndex" );
		wFlags  = CTRLF_HASCUSTOM;
		if( !hContact || DB::Setting::Exists( hContact, szProto, SET_CONTACT_TIMEZONE ) )
		{
			wFlags |= CTRLF_HASPROTO;
		}
	}
	else if( dwIndex != ( DWORD )-1 && myGlobals.TzIndexExist == 1 ) {
		// contact has old TzIndex and Win TzIndex info exist
		wFlags  = CTRLF_HASCUSTOM;
		if( !hContact || DB::Setting::Exists( hContact, szProto, SET_CONTACT_TIMEZONE ) )
		{
			wFlags |= CTRLF_HASPROTO;
		}
	}
	else
	{
		DBVARIANT dbv;
		TZ_MAP MirTZ;
		dbv.type = DBVT_DELETED;

		// contact has TzIndex info, but Win Reg has no TzInfo -> delete old contact TzInfo
		if( dwIndex != ( DWORD )-1)
			DB::Setting::Delete( hContact, USERINFO, "TzIndex" );

		// try to get miranda's timezone value
		wFlags = DB::Setting::GetAsIsCtrl( hContact, USERINFO, USERINFO, szProto, SET_CONTACT_TIMEZONE, &dbv );
		if( dbv.type == DBVT_BYTE )
		{
			CTimeZone key;
			MirTZ = IndexFromMirandaTimeZone( dbv.cVal );
			key.dwIndex = MirTZ.Index;
			key.pszName = MirTZ.Name;
			if( key.dwIndex != ( DWORD ) -1 /*&& _name.find( &key )*/ )
			{
				dwIndex = key.dwIndex;
				pszName = key.pszName;
			}
			else
			{
				wFlags = 0;
			}
		}
		else
		{
			wFlags = 0;
			DB::Variant::Free( &dbv );
		}
	}
	return wFlags;
}

/**
 * This method looks up the contact's database settings for information about his timezone.
 * If it is somehow possible, the method writes all available timezone information into the
 * @e tzi structure
 *
 * @param	hContact		- the HANDLE of the contact to read timezone information for
 * @param	szProto			- contact's protocol
 * @param	tzi				- pointer to a TIME_ZONE_INFORMATION structure, which will retrieve the information
 *
 * @retval	0		- the timezone was found and successfully written to @e tzi
 * @retval	1		- there are no timezone information available for the contact
 **/
INT CTzManager::GetContactTimeZoneInformation( HANDLE hContact, LPCSTR szProto, TIME_ZONE_INFORMATION& tzi ) const
{
	if( _index.getCount() )
	{
		DWORD dwIndex;
		LPTSTR pszName;
		CTimeZone *ptz;

		ptz = GetContactTimeZone( hContact, szProto, dwIndex, pszName);

		if( !ptz )
		{
			const CHAR c = ( CHAR ) dwIndex;

			// fall back and use owners tz info for additional information
			if( c >= -24 && c <= 24 )
			{
				GetTimeZoneInformation( &tzi );
				tzi.Bias = c * 30;
				return 0;
			}
		}
		else
		{
			tzi = *ptz;
			if (pszName) mir_free(pszName);
			return 0;
		}
	}
	return 1;
}
